5 1* {3 C-ohjelmointikurssi - Osa 1 {3 --------------------------- Ville-Pertti Keinonen Ensimmäinen osa: - johdantoa - lähdekoodi - kääntäjä - muotoilu - funktiot, tyypit ja muuttujat alustavasti - lauseet, lausekkeet (perus-operaatiot) - vertailuoperaattorit, if-rakenne {3C-kieli ja muut kielet, eli miksi kannattaa opetella C:tä Alunperin C-kieli on kehitetty UNIX-käyttöjärjestelmän toteutusta varten PDP-tietokoneilla. Kieli onnistui kuitenkin siinä määrin hyvin, että se on yleistynyt lähes kaikentyyppisillä tietokonelaitteistoilla. Amigalla C-kieli on ohjelmointikielistä ehkä merkittävin, koska Amigan käyttöjärjestelmä on suurelta osin sillä kirjoitettu. C-kieli on rakenteellinen ohjelmointikieli. Sen hallinta vaatii enemmän har- joitusta kuin matalamman tason (esim. assembler) tai korkeamman tason (Ami- galla pääasiassa BASICit ja ARexx) kielet, mutta siitä huolimatta sen opet- teleminen on vaivan arvoista, sillä C-kielellä on huomattavia etuja molem- piin luokituksiin verrattuna: Matalamman tason kieliin verrattuna C-kieli on vähemmän vaivalloista ja bu- gialtista. Ohjelmointi on siis huomattavasti nopeampaa ja mukavampaa. Samoin mahdollisten bugien määrä on pienempi ja ilmenevien bugien etsintä nopeam- paa, kuin assemblerilla ohjelmoitaessa. Monilla assembleria käyttävillä oh- jelmoijilla, jotka eivät osaa C:tä, on vahvasti se käsitys, että C olisi BA- SICin kaltainen korkean tason kieli ja tuottaisi hidasta koodia. Totta on, että C-kääntäjän tuottama koodi ei ole aivan niin tehokasta kuin vastaava koodi huolellisesti assemblerilla tehtynä (mihin kuluu ohjelmoijalta aikaa vähintään kymmenkertaisesti enemmän), mutta ero ei ole normaalitilanteissa kovin suuri; se lienee noin 20-30% yksinkertaisilla kääntäjillä ja vähemmän sellaisilla, jotka optimoivat tehokkaasti tuottamaansa koodia. Kuitenkin joissain poikkeustilanteissa, joissa assemblerissa voisi tehdä jotain eri- koisempia teknisiä optimointeja, voi ero olla niin suuri, että moni ohjel- moija tekee ainakin kyseisen kohdan mieluummin assemblerilla. Mitä BASICin- kaltaisuuteen tulee, osoittaa sen tyyppinen käsitys lähinnä ymmärtämättömyyttä, sillä: Korkeamman tason kieliin verrattuna C tuottaa käännettynä paljon tehokkaam- paa koodia, koska sen rakenteet vastaavat hyvin läheisesti sitä, mitä kesku- syksikkö käytännössä tekee. Korkeamman tason kielet ovat usein komentopoh- jaisia, eivätkä niiden muuttujien tyypit vastaa keskusyksikön käsittelemiä, joten jokaista komentoa tai lausekkeen osaa vastaa jokin kokonainen assemb- ler-rutiini, joka on useimmiten aivan turhan monimutkainen kyseisen operaa- tion suorittamiseen. Amigalle on ilmestynyt uusi, ilmeisesti C:n haastajaksi tarkoitettu mel- kein-rakenteellinen ohjelmointikieli, E, joka on helpompaa kuin C ja jossa on nopeampi kääntäjä (joka tosin ei pahemmin optimoi tuotettua koodia) ja mahdollisuus käyttää helpommin suoraan assemblerkomentoja koodin joukossa. Omasta mielestäni E ei kuitenkaan sovellu oikein mihinkään, paitsi ehkä sel- laisiin aivan pieniin ohjelmiin, jotka ovat assembleria, lukuunottamatta jo- tain kutsuja E:n tai käyttöjärjestelmän korkeamman tason rutiineihin. {3C-kielen kehitys C-kieli on ajan mittaan muuttunut hieman alkuperäisestä muodostaan. Jotkut asiat ovat olleet epäselviä, ja niiden toiminta on vaihdellut kääntäjästä toiseen. Tämän vuoksi ANSI (American National Standard for Information Sys- tems) loi X3J11:n, teknillisen toimikunnan, jonka tehtävänä oli standardi- soida C-kieli. Tämä standardisoitu muoto C-kielestä tunnetaan nykyisin ylei- sesti "ANSI C":nä. Muista C:n "määritelmistä" lienee merkittävin K&R (viit- taa Brian Kernighanin ja Dennis Ritchien teokseen C-kielestä), johon ANSI C osin pohjautuu. Suurin osa Amigan C-kääntäjistä on pääpiirteiltään ANSI C-standardin mukai- sia. Useimmiten kuitenkin joitain asioita on laajennettu, ja kääntäjät ovat joidenkin asioiden suhteen suvaitsevampia kuin standardin mukaisen kääntäjän tulisi olla. Tässä kurssissa pyrin parhaiden tietojeni mukaan mainitsemaan kyseisenlaiset poikkeamat niihin liittyvän materiaalin yhteydessä. {3Tämä kurssi Tämän kurssin tarkoituksena on selittää C-kielen perusteet ja antaa hieman pitemmällekin menevää tietoa sillä ohjelmoinnista, eli kurssista lienee apua myös monille sellaisille, jotka jo osaavat C:tä. Pelkkä kurssin lukeminen ei kuitenkaan riitä C-kielen ohjelmointiin: täytyy myös pyrkiä ymmärtämään lukemansa, joten suosittelen, että luette kaiken huolellisesti ja etenette seuraavaan kohtaan vasta ymmärrettyänne edellisen kohdan mahdollisimman hyvin. Muistakaa myös, että kertaus ja käytännön ko- keilut auttavat muistamaan asioita. Tämä kurssi ei takaa ohjelmointitaidon kehittymistä, mutta jos kykenee ymmärtämään sisällön ja näiden tietojen pe- rusteella lähtee rohkeasti kokeilemaan ja harjoittelemaan ohjelmointia, pitäisi vähitellen syntyä C-kieleen hyvä tuntuma, jonka avulla voikin jo aloittaa varsinaisen ohjelmoinnin. Tämä kurssi ei sinänsä vaadi mitään aiempaa ohjelmointitaitoa, mutta assemb- lerin jonkinasteinen hallitseminen on hyödyksi, sillä se helpottaa koneen toiminnan ja joidenkin käsitteiden käytännön merkityksen ymmärtämistä. Jon- kinlainen C-kääntäjä on hyvä olla, jotta voi myös kokeilla oppimiaan asioi- ta. Amigalle on saatavilla ainakin seuraavia kääntäjiä: {3Aztec (Manx) C Tätä ei varmaan enää moni käytä. Aztec on vanhanaikainen (ja vanha) kääntäjä, aikoinaan se on ollut ilmeisesti aika suosittukin. En tiedä, onko tätä kääntäjää enää mistään saatavilla, enkä näe mitään syytä, miksi joku haluaisi vielä nykyään käyttää Aztecia. {3DICE C DICE on alunperin Matthew Dillonin sharewarena julkaisema C-kääntäjä. Uudet versiot ovat kaupallisia, mutta vapaasti levitettäviä versioita on vielä liikkeellä, ja ne ovat ominaisuuksiltaan jo erittäin käyttökelpoisia. Vaikka koneessa ei olisi paljon muistia, DICE toimii hyvin ja kääntää ohjelmat erittäin nopeasti. DICE:n tuottama koodi on suhteellisen pientä ja nopeaa, vaikka DICE ei suorita sille mitään monimutkaisempia optimointeja. Itse käytän enimmäkseen DICE:n rekisteröityä shareware-versiota ohjelmien kääntämiseen. {3GNU C (eli GCC) GCC on muista järjestelmistä portattu C-kääntäjä, joka on ilmainen (GNU li- cense takaa ohjelman olevan täysin ilmainen, vapaasti käytettävissä, levi- tettävissä ja jopa muuteltavissa). GCC kääntää ohjelmia kohtuullisen nopeas- ti ja optimoi hyvin tehokkaasti, mutta vaatii suhteellisen paljon muistia. GCC:n tuottamat ohjelmat vaativat ixemul.libraryn. GCC soveltuu parhaiten käytettäväksi UNIX-tyyppisiä, helposti eri järjestelmille suoraan käännettäviä ohjelmia tehtäessä. {3SAS/C (entinen Lattice C) SAS/C on ollut jo pitkään Amigan suosituin C-kääntäjä. SAS/C on kaupallinen, mutta sen kehitys on lopetettu. SAS/C toimii suhteellisen vähällä muistilla ja kääntää kohtuullisen nopeasti, jos optimointi on pois päältä, mutta tuot- taa erittäin hidasta koodia. Optimoinnin kanssa tuotettu koodi on paljon te- hokkaampaa, mutta kääntäminen on erittäin hidasta ja muistia kuluu monissa tapauksissa valtavia määriä. Myös joitain pienempiä, ilmaisia kääntäjiä on saatavilla (ainakin PDC ja North C), mutta ne ovat vanhoja ja hyvin alkeellisia. Jossain vaiheessa pitäisi ilmestyä Amigalle vielä yksi merkittävä ilmainen C-kääntäjä, joka lienee harkitsemisen arvoinen. Olen nimittäin itse te- kemässä kääntäjää. Toistaiseksi sillä ei ole vielä nimeä, mutta kääntäjän pääosa on jo varsin pitkällä ja toimii hyvin niin pitkälle kuin sitä on ole- massa. Generoidun koodin pitäisi olla GCC:n tuottaman tasoa (myös joitain GCC:n ja muiden kääntäjien C-laajennuksia, kuten case rangeja ja __ty- peof__:ia, tuetaan), mutta lisäksi toiminta soveltuu hyvin Amiga- ympäristöön. Mainostan vain etukäteen, ettei kannata välttämättä ainakaan ostaa C-kääntäjää, kurssin ajaksi ja muutenkin aloitteluvaiheeseen on DICE:n ilmaisversio useimmille ehkä paras vaihtoehto. Täydellisempi kääntäjä olisi tietysti GCC, joka on hyvä valinta, jos pitää erityisesti UNIX-tyyppisistä ohjelmistoista ja omistaa riittävästi kovalevy- sekä muistitilaa. Kääntäjän lisäksi on tietysti oltava jokin tekstieditori, jolla syöttää oh- jelmat koneelle. Tällaisia on varmasti jokaisen saatavilla; Amigan Work- bench-diskeillä tulee näitä jo kolme: ed, edit, memacs. Ne riittävät jo hy- vin C-ohjelmoinnin opetteluun, mutta pitemmälle päästäessä on hyvä hankkia jokin monipuolisempi (configuroitavuudeltaan) editori. Kurssi pyrkii olemaan mahdollisimman kattava, mutta kuitenkin selkeä. Alussa joudutaan käyttämään joidenkin asioiden esittämisessä esimerkkejä, joiden varsinaisen sisällön merkitys selviää vasta myöhemmässä vaiheessa, erityi- sesti koska kurssi pyrkii olemaan "oikeaoppinen" alusta alkaen. Kurssissa käytetyt termit saattavat poiketa virallisista suomenkielisistä termeistä, koska itse tunnen oikeat termit vain englanniksi. Monet asiat on selitetty hieman kierrellen jonkun hankalan tai oudon kuuloisen termin käytön välttämiseksi. Joissain yhteyksissä on myös englanninkielinen termi mainittu. {3Lähdekoodi ja käännös C-kieltä ohjelmoitaessa kirjoitetaan ensin ohjelma tekstieditorilla ja tal- lennetaan se tiedostoon. Tätä tiedostoa sanotaan lähdekoodiksi. Yleisemmin käytetään kuitenkin vastaavasta englanninkielisestä termistä "source code" johdettua slanginimitystä "sorsa". C-kielen lähdekooditiedostot nimetään yleensä käyttäen päätettä ".c". Jotta ohjelma saadaan lähdekoodista ajettavaksi tiedostoksi, täytyy se kääntää jollain C-kääntäjällä. Käännöksessä on useita vaiheita, joita käsi- tellään myöhemmin. Ohjelmoijan ei yksinkertaisia ohjelmia tehdessä tarvitse huomioida eri vaiheita, koska C-kääntäjän liittymäosa osaa useimmiten ajaa kaikki käännöksen vaiheet automaattisesti. Hyvin yleistä on kuitenkin, ettei kääntäjä kykene kääntämään lähdekoodia ajettavaksi ohjelmaksi, koska siinä on virheitä. Tällöin kääntäjä ilmoittaa virheistä ja kertoo yleensä ainakin virheen tyypin sekä rivin, jolla se lähdekoodissa esiintyy. Kun näin tapahtuu, kannattaa katsoa lähdekoodin ri- viä, jolla kääntäjä ilmoittaa virheen olevan. Yleensä virheet, joista kääntäjä ilmoittaa, ovat välittömästi näkyvissä olevia (esimerkiksi kirjoi- tusvirheitä). Jos kyseiseltä riviltä ei löydy mitään, mikä vaikuttaa vir- heelliseltä, kannattaa katsoa paria edellistä riviä. Monet kääntäjät eivät yhdestä virheestä keskeytä käännöstä, vaan jatkavat eteenpäin, jolloin virheitä saatetaan luetella useampia. Useimmat kääntäjät saattavat myös varoittaa jostain asioista. Nämä varoitukset eivät ole varsi- naisia virheitä, ja ohjelma yleensä kääntyy varoituksista huolimatta. Ne saattavat kuitenkin tarkoittaa, ettei ohjelma tule toimimaan aivan kunnolla, eli ohjelman ei pitäisi käännettäessä tuottaa edes varoituksia. Kun ohjelma lopulta kääntyy kunnolla ilman virheitä tai varoituksia, pitäisi tuloksena olla ajettava tiedosto, jota voi jo kokeilla. Kuitenkaan se, että kääntäjä on onnistunut kääntämään ohjelman lähdekoodista ajettavaan muotoon, ei takaa ohjelman toimivan halutusti. Ohjelmassa voi olla kaikenlaisia suun- nitteluvirheitä, "bugeja", jotka saattavat olla hyvinkin vaikeasti löydettävissä. Tällaisten vikojen etsintää eli "debuggausta" käsitellään kurssissa vasta myöhemmin, kun on aluksi käyty läpi itse C-kielen perusteet. Kurssin esimerkkiohjelmien pitäisi kuitenkin toimia millä tahansa kunnolli- sella C-kääntäjällä käännettynä. (Niiden esimerkkiohjelmien kohdalla, joita tämä ei koske, on erillinen maininta.) Tässä vaiheessa voimmekin jo kokeilla yksinkertaisen ohjelman kääntämistä. Käynnistä tekstieditori ja kirjoita siihen seuraava: #include int main(int ac, char **av) {{ puts("Hello World!"); return 0; } Tallenna ohjelma vaikka tiedostoon nimeltä "hello.c". Tämä hello.c on ohjel- man lähdekoodi, kuten ylempänä selitettiin. Nyt voidaan kokeilla käyttää C- kääntäjää, jotta saadaan tämä hello.c käännettyä ajettavaan muotoon, vaikka tiedostoksi nimeltä "hello". Käännös tapahtuu ylläkäsitellyillä kääntäjillä seuraavasti: {3Aztec (Jos joku käyttää Aztecia, voi ehkä osata tehdä järkevämminkin.) Yleisesti: cc .c ln .o -l Eli hello.c käännetään: cc hello.c ln hello.o -lhello {3DICE C Yleisesti: dcc -o Eli hello.c käännetään: dcc hello.c -o hello {3GCC Yleisesti: gcc -o Eli hello.c käännetään: gcc hello.c -o hello {3SAS/C Yleisesti: sc link to Eli hello.c käännetään: sc hello.c link to hello Kaikki kääntäjät osaavat paljon muutakin, joten kannattaa tutustua huolelli- sesti käyttämäänsä kääntäjään. Jos käännös on onnistunut, pitäisi tuloksena olla ajettava tiedosto "hello". Kokeile ajaa se; sen pitäisi tulostaa "Hello World!" CLI/Shell-ikkunaan, josta se ajettiin. {3Välissä kehittyneempää tietoa kiinnostuneille Yksi syy, miksi C-kieltä on virheellisesti pidetty tehottomana, on se, että tällainenkin esimerkki tuottaa varsin suuren ajettavan tiedoston. Tämän "hello":n koko käännettynä on kääntäjäkohtaisesti n. 2-5kB. Tästä kuitenkin itse lähdekoodissa oleva osuus on enintään hieman yli 200 tavua, vaihdellen kääntäjän ja käytettyjen optioiden mukaan. Kokonaisuuden suhteessa suuri ko- ko johtuu siitä, että ohjelman lopullisessa ajettavassa muodossa on linkat- tuna mukaan kaksi alustuskooditasoa. (Linkkauksesta puhutaan myöhemmin kurs- sissa.) Niistä matalampitasoinen yleensä tallentaa pino-osoittimen arvon (jotta ohjelmasta voidaan poistua helpommin riippumatta poistumiskohdasta), avaa dos.library-systeemikirjaston ja varautuu siihen, että ohjelma on käyn- nistetty Workbenchistä. Tämän jälkeen kutsutaan korkeamman tason alustuskoo- dia, joka valmistelee C:n stdio-tiedostot ja muotoilee komentorivillä anne- tut parametrit C:n main():in käyttämään muotoon. Vasta kaiken tämän jälkeen kutsutaan main():ia eli varsinaista käyttäjän tekemää ohjelmaa. Alustuskoodi itsessäänkään ei vie niin paljon tilaa, vaan ohjelmaan joudutaan linkkaamaan myös C:n tavallisia funktioita, hello.c:n tapauksessa ainakin seuraavia: fo- pen(), fclose(), fwrite(), write(), puts() ja malloc(). (GCC on tässä poik- keus; osa näistä rutiineista on sijoitettu ixemul.library-kirjastoon, jol- loin niitä ei tarvitse sisällyttää jokaiseen ohjelmaan erikseen.) Suuremmissa ohjelmissa koon kasvu linkatun informaation takia ei ole niin merkittävää, koska niissä itse ohjelman osuus on huomattavasti suurempi. Pieniä ohjelmia tehdessään monet kuitenkin haluavat minimoida ohjelmiensa koon jättämällä kaikki C:n alustukset pois. Tämä vaatii jonkinasteista Ami- gan käyttöjärjestelmän tuntemusta, koska vain joitain harvoja yksinkertaisia (lähinnä merkkijonon manipulointiin käytettyjä) C:n standardifunktioita voi- daan kutsua. Tällä tavalla tehtynä esimerkisi hello.c voitaisiin tehdä seu- raavasti: #include #include #include #ifdef __GNUC__ #include #include int callstart(void) {{ return start(); } #else #include #include #include #include #endif int start(void) {{ struct Library *SysBase, *DOSBase; SysBase = *(struct Library **)4; if (DOSBase = OpenLibrary("dos.library", 0)) { Write(Output(), "Hello World!\n", 13); CloseLibrary(DOSBase); } return 0; } Tämän pitäisi kääntyä ainakin DICE:lla, SAS/C:llä ja GCC:llä, jos osaa aset- taa optiot oikein ja omistaa tarpeelliset includet. Tuloksena olevan ajetta- van ohjelman koon pitäisi olla alle 130 tavua DICE:lla tai SAS/C:llä käännettynä, GCC:llä tämän tyyppinen tekniikka toimii varsin huonosti. Pal- joa tätä pienemmäksi ei vastaavaa ohjelmaa saisi assemblerillakaan. Kokoa voisi hieman vähentää jättämällä pois rekisterien tallentaminen pinoon, joka poikkeuksellisesti ei ole tässä tarpeellista. {3C-kielen muotoilu C-kieli on hyvin vapaamuotoista: siinä voi olla jokaisen elementin välissä rajoittamaton määrä "tyhjää" (tyhjäksi lasketaan välilyönnit, tabulaattorit, rivinvaihdot sekä kommentit). Ottakaamme esimerkiksi hello.c:ssä käytetty funktion puts() kutsu: puts("Hello World!"); puts ( "Hello World!" ) ; puts ( "Hello World!" ) ; Kaikki ylläolevat tarkoittavat C-kääntäjän näkökulmasta samaa, koska ne koostuvat samoista viidestä elementistä: "puts", "(", "Hello World!", ")" ja ";". Kuten useimmissa muissakin kielissä, myös C-kielessä voidaan ohjelman lähde- koodiin kirjoittaa kommentteja, jotka eivät vaikuta itse ohjelmaan mi- tenkään, koska kääntäjä ei huomioi niitä. C-kielen kommentit alkavat mer- keillä "/*" ja loppuvat merkkeihin "*/" ja voivat sijaita lähes missä vain. Yllä esimerkkinä käytetty tarkoittaisi edelleen samaa, vaikka sen kirjoit- taisi muotoon: puts /* this is a comment */ ( "Hello World!" /* * this is a block comment */ ); Lisäksi monet C-kääntäjät hyväksyvät C++-tyylisiä kommentteja. Nämä alkavat merkeillä "//" ja jatkuvat rivin loppuun. Edelleen samaa esimerkkiä käyttäen: puts ( // This is a C++ comment "Hello World!"); Aivan erityinen "tyhjän" merkki on "\" rivin lopussa. Tällä ilmaistaan ri- vin jatkumista seuraavan rivin alusta, ja se voi vaikka jakaa jonkun normaa- listi yhtenäisen koodin osan muuttamatta sen merkitystä: pu\ ts("Hello World!"); Rivin lopussa oleva "\" ei kuitenkaan yleensä ole tällaisissa tilanteissa hyödyllinen. Käytännössä sitä käytetään yleisimmin esikäsittelijän ohjausko- mentojen yhteydessä. Ne ovat varsinaiseen C-koodiin nähden poikkeuksellisia, koska ne ovat aina rivin mittaisia. Näistä lisää myöhemmin. Joillakin kääntäjillä voi myös kommenteilla hajottaa osia muuttamatta niiden merkitystä. Seuraava toimii useilla kääntäjillä: pu/* */ts("Hello World!"); Tällainen saattaa myös toimia: pu/* */ts("Hello World!"); Näiden kommenttijakojen toimivuuteen ei kuitenkaan kannata luottaa. Usein vaaditaan osien välille vähintään yksi tyhjä, jotta ne voidaan erottaa toisistaan, esimerkiksi hello.c:ssä on eräällä rivillä: int main(int ac, char **av) Mahdollisimman paljon tiivistettynä tämä olisi: int main(int ac,char**av) Tämän enempää ei voida tiivistää, koska "intmain" tai "intac" näyttäisivät yhdeltä "sanalta". Vaikka C-kieltä voidaan muotoilla näin vapaasti, käytetään kuitenkin yleensä täysin säännönmukaista muotoilua ohjelman rakenteen selkeyden vuoksi. Mitään virallista tapaa ei varsinaisesti ole, mutta on kuitenkin "yleinen käytäntö", jota useimmat C-ohjelmoijat seuraavat. Tämä käytäntö ei kuiten- kaan ole kovinkaan tarkka, vaan aika yleinen, joten jotkut asiat vaihtelevat aina ohjelmoijan mukaan. Yleiseen käytäntöön kuuluu, että jokainen ohjelman lohko ("{"- ja "}"-merk- kien välissä oleva ohjelmaosuus) sisennetään. Kuitenkin sisennyksen määrä ja aaltosulkeiden sijoittelu vaihtelee. Yleensä sisennys on 2-4 merkkiä. Jotkut käyttävät 8 merkin sisennystä, mikä tosin vaikeuttaa ohjelman rakenteen hah- mottamista jo siinä määrin, että ohjelmoijan on vaikea hyödyntää C:n raken- teellisuuden etuja, ja ohjelman tekninen toiminta kärsii (oma "teoria" - ei ole pakko uskoa). Tässä pari esimerkkiä sisennyksestä ja aaltosulkeiden si- joittelusta (typerä esimerkkifunktio, ei kokeiltavaksi tai ymmärrettäväksi tarkoitettu): /* * yksinkertainen sijoittelu, 2 merkin sisennykset */ void procedure(void) {{ int i, s; for (i = 10, s = 5; i; --i) { s += i * 3; if (i == 5) { printf("at i = 5, s = %d\n", s); i -= s / 10; } else { printf("loop iteration %d\n", i); s = -s; } } puts("done"); } /* * hieman erikoisempi sijoittelu, 4 merkin sisennykset */ void procedure(void) {{ int i, s; for (i = 10, s = 5; i; --i) { s += i * 3; if (i == 5) { printf("at i = 5, s = %d\n", s); i -= s / 10; } else { printf("loop iteration %d\n", i); s = -s; } } puts("done"); } /* * tiiviimpi sijoittelu, 2 merkin sisennykset * (tässä kurssissa käytetty muoto) */ void procedure(void) {{ int i, s; for (i = 10, s = 5; i; --i) { s += i * 3; if (i == 5) { printf("at i = 5, s = %d\n", s); i -= s / 10; } else { printf("loop iteration %d\n", i); s = -s; } } puts("done"); } Ylläolevissa esimerkeissä tulee esille myös muita muotoiluun liittyviä asioita, kuten välien käyttö. Kun kutsutaan/määritellään funktioita (tässä niitä ovat "procedure", "printf" ja "puts"), ei yleensä laiteta väliä sul- keiden ympärille eikä sisäpuolelle. (Tosin jotkut - aika harvat - käyttävät väliä rutiinin nimen ja "("-merkin välissä.) Kun taas käytetään C:n raken- teita (tässä: "for", "if"; muita: "while", "switch"), laitetaan yleensä sul- keiden ympärille välit (jälleen kerran poikkeuksia löytyy). Sulkeiden sisäpuolella harvat käyttävät missään yhteydessä välejä. Pilkun jälkeen tulee aina väli, ennen pilkkua ei. (Tosin tämäkin voi vaih- della pilkkun käyttöpaikan mukaan.) Lausekkeissa yleensä käytetään välejä eri operaattoreiden ("=", "==", "+", "-" jne.) molemmin puolin, poikkeuksena esimerkiksi "-" silloin, kun se tar- koittaa negatiivista arvoa eikä vähennyslaskua. Kommentit sijoitetaan yleensä rivin loppuun tai tyhjälle riville. Usein tyhjälle riville laitettu kommentti kannattaa laittaa useammalle riville esimerkkien selostuksissa näkyvään tyyliin. Enemmänkin "käytäntöjä" on, mutta ne käyvät ilmi myöhemmin itse kielen opet- telun yhteydessä. Jos joutuu lukemaan paljon toisten kirjoittamaa C-koodia ja on muotoilun suhteen yhtä neuroottinen kuin minä, kannattaa etsiä käsiinsä "indent"- oh- jelma, joka automaattisesti muotoilee C-sorsia sille annettujen optioiden mukaisesti. {3Funktiot C-kielisen ohjelman eräitä oleellisia osia ovat funktiot (tai "rutiinit", englanniksi "functions" tai "procedures"). Funktioita voidaan määritellä ja kutsua. Esimerkkiohjelma hello.c määritteli funktion main() ja kutsui funk- tiota puts(): int main(int ac, char **av) /* funktion esittely */ {{ /* funktion koodin määrittely alkaa */ puts("Hello World!"); /* kutsutaan funktiota puts() */ return 0; /* palautetaan 0 */ } /* funktion koodin määrittely loppuu */ Funktion esittely määrittelee funktion nimen, minkätyyppisen arvon funktio palauttaa sekä minkätyyppiset parametrit sillä on (tyypeistä tarkemmat se- lostukset myöhemmin). Esimerkissä funktion nimi on main, se palauttaa arvon, jonka tyyppi on int (eräs kokonaislukutyyppi), parametrien tyypit ovat int ja char **, joille annetaan muuttujanimikkeet ac ja av funktion sisällön ajaksi, eli ne ovat käytettävissä kuten mitkä tahansa paikalliset muuttujat (muuttujistakin lisää tietoa myöhemmin). Funktion esittelyn muuttujien määrittelylle on myös toinen tapa. Ylläoleva ja tässä kurssissa yleensä käytetty on ANSI:n mukainen tapa. Jotkut käyttävät vanhaa K&R tyyliä: int main(ac, av) int ac; char **av; {{ puts("Hello World!"); return 0; } Funktiolla main() on erityinen merkitys, sillä C-ohjelman suoritus kutsuu sitä automaattisesti. Tämän takia ovat main():in parametrien ja palautuksen tyypit aina samat, vaikka ohjelma ei käyttäisikään parametreja mihinkään. Funktion varsinainen sisältö tulee esittelyn jälkeen aaltosulkeiden välissä. Funktion sisältö määrää, mitä funktio tekee, kun sitä kutsutaan. Kun funktiosta poistutaan, se voi palauttaa jonkin arvon. Funktiomme main() palauttaa arvon, joka on tyypiltään int, kuten on määritelty funktion esit- telyssä. Arvo palautetaan komennolla (yksi C-kielen kahdesta komennosta) "return", jolla asetetaan palautusarvo ja poistutaan funktiosta. hello.c:n tapauksessa on palautusarvona luku 0. Mitään return:in jälkeisiä toimintoja funktiossa ei suoriteta, jos muuttaisimme järjestystä hello.c:ssä: int main(int ac, char **av) {{ return 0; puts("Hello World!"); /* ei koskaan suoriteta */ } Tässä ei ohjelman suoritus edes pääsisi puts():in kutsuun, koska funktiosta on poistuttu jo ennen sitä. Komentoa "return" voidaan käyttää myös ilman palautusarvoa, jos funktio on määritelty sellaiseksi, joka ei palauta mitään (eli esittelyssä on palautus- tyypiksi määritelty pseudo-tyyppi "void"). Tyyppiä "void" voidaan käyttää myös ilmaisemaan, ettei funktiolle anneta parametreja. /* * funktio, joka ei ota parametreja eikä palauta mitään */ void func1(void) {{ return; } /* * kun funktio ei palauta mitään, voidaan return-komento jättää pois, * sillä funktiosta poistutaan automaattisesti myös kun funktio loppuu */ void func2(void) {{ } Seuraavaksi esimerkkiohjelma, joka demonstroi sellaisen funktion kutsumista, joka ei ota parametreja: #include void test(void) {{ puts("testing..."); } int main(int ac, char **av) {{ test(); puts("ok"); return 0; } Tämän esimerkin pitäisi tulostaa shell-ikkunaan: testing... ok Tässä funktion test() voisi määritellä vanhaan tyyliin ilman "void":ia para- metrien korvikkeena: void test() {{ puts("testing..."); } Funktioiden määrittelemistä ja kutsumista käsitellään myöhemmin enemmän. Aluksi on kuitenkin syytä perehtyä hieman tarkemmin tyyppeihin ja muuttu- jiin. {3Muuttujat ja tyypit alustavasti C-kielessä on muuttujille määriteltävissä useita eri tyyppejä. Perustyyppejä on tehokas käsitellä, koska ne ovat samoja tyyppejä, joita koneen prosessori osaa käsitellä suoraan. Ennen tyyppien selitystä ja muuta kurssissa tulevaa on syytä käsitellä joi- takin tietokoneiden toimintaan liittyviä peruskäsitteitä, jotka lienevät mo- nille jo ennestään tuttuja: Bitti (englanniksi "bit"): Nykyisten tietokoneiden toiminta pohjautuu digi- taalisiin signaaleihin (vastakohtana olisivat esimerkiksi stereolaitteet, joissa käytetään analogisia signaaleita), joilla on vain kaksi mahdollista tilaa. Näistä tiloista käytetään numeerisia ilmaisuja 0 ja 1. Käytännössä nämä signaalit liikkuvat sähkönä johtimia pitkin, ja numeerisia merkintöjä vastaavat yleensä jännitteet 0V (eli 0) ja +5V (eli 1). Tietoa käsiteltäessä tällaisesta yksittäisestä signaalista (ja samalla tiedon pienimmästä mahdol- lisesta yksiköstä) käytetään nimitystä bitti. Tavu (englanniksi "byte"): Koska bitillä voi ilmaista vain lukuja 0 ja 1, yhdistetään yleensä useampia bittejä, jotka yhdessä voivat esittää hyödylli- sempiä lukuja (käytännössä tämä tehdään rinnakkaisilla johtimilla). Yleensä pienin tällä tavalla käsitelty tiedon yksikkö on tavu, eli 8 rinnakkaista bittiä. Tavun bitit numeroidaan nollasta seitsemään. Tavun arvo on päällä (eli tilassa 1) olevien bittien arvojen summa. Bitin arvo on 2^n (jossa ^ tarkoittaa potenssiin korotusta ja n on bitin numero). Bitti numero 7 on siis tavun eniten merkitsevä, ja bitti numero 0 vähiten merkitsevä bitti. Kun tavun arvo kirjoitetaan binäärimuotoon (2-kantainen luku), aloitetaan eniten merkitsevästä bitistä. Heksadesimaalinotaatio: Koska 10-kantaiset luvut eivät sovellu kovin hyvin tietokoneissa käytettäviksi (ne eivät mene bitteinä tasan), käytetään moniin tarkoituksiin 16-järjestelmää (heksadesimaalilukuja), jossa käytetään nume- roiden 0-9 lisäksi kirjaimia a-f lukujen kirjoittamiseen. Heksadesimaalilu- vun tunnuksena on C-kielessä 0x (assemblerissa se on $). Heksadesimaalilu- vuissa on se etu, että jokainen numero vastaa suoraan neljää bittiä, eli ta- vun arvo on aina ilmaistavissa kahdella heksanumerolla. Tavun arvo on siis väliltä 0x00 (desimaalina 0) - 0xff (desimaalina 255). Muisti: Tiedon ilmaisemisesta hetkellisinä signaaleina ei ole sellaisenaan mitään hyötyä, vaan tietoa täytyy voida säilyttää jotenkin. Tieto säily- tetään muistin avulla, joka on periaatteessa vain alue peräkkäisiä tavuja. Yleisimmin muistina käsitetty muisti on RAM-muistia ("Random Access Memo- ry"), johon voidaan kirjoittaa ja josta voidaan lukea. Amigassa dos-komento "avail" kertoo vapaana olevan RAM-muistin määrän, miten paljon sitä enim- millään voisi olla ja miten paljon sitä on käytössä. (Amigassa RAM-muisti on edelleen jaoteltu kahdentyyppiseen muistiin: chip-muisti, joka on keskusyk- sikön lisäksi kaikkien erikoisprosessoreiden osoitettavissa ja fast-muisti, joka on nopeampaa, koska keskusyksikkö saa sillä enemmän väyläaikaa.) Muis- tin suhteen ei sinänsä ole olemassa käsitteitä "vapaana" tai "käytössä", vaan Amigan käyttöjärjestelmä (kuten useimmat muutkin käyttöjärjestelmät) pitää lukua RAM-muistin käytöstä. Muistin "määrä" lasketaan tavuissa. Lisäksi on ROM-muistia ("Read Only Memory"), jota voi vain lukea. Amigassa keskeisimmät osat käyttöjärjestelmästä ovat ROM-muistissa. (Juuri tästä on kyse, kun puhutaan "Kickstart ROM":ista.) Muistiosoite: Muistia käsiteltäessä on kyettävä ilmaisemaan, mitä kohtaa muistissa halutaan käsitellä. Tämän takia muisti on kartoitettu osoitteiksi. Periaatteessa muistiosoite ilmoittaa vain, monesko tavu muistia on kyseessä, tosin kaikissa osoitteissa ei välttämättä ole muistia. Motorolan 680x0-sar- jan prosessoreissa (joita Amigassa käytetään) muistiosoitteet ovat 32-bitti- siä lukuja (tosin 68000/68010 huomioivat vain vähiten merkitsevät 24 bit- tiä), eli neljän tavun mittaisia. Muistiosoitteet voivat siis periaatteessa olla väliltä 0 - 0xffffffff (desimaalina 4294967295 - hyvä esimerkki siitä, miksi heksadesimaaliluvut ovat käytännöllisiä). Amigan RAM-muistista chip- muisti alkaa yleensä osoitteesta 0 ja jatkuu enintään osoitteeseen 0x1fffff (jos on 2MB chip-muistia). 16-bittinen fast-muisti (60000/68010-pohjaisissa koneissa oleva fast-muisti) sijaitsee osoitteesta 0x200000 alkaen enintään 8MB eteenpäin. 32-bittinen fast-muisti (jonka osoittamiseen tarvitaan vähintään 68020) alkaa osoitteesta 0x7800000. Amigan Kickstart ROM sijaitsee joko osoitteesta 0xf80000 (2.0 ja uudemmat versiot) tai 0xfc0000 osoittee- seen 0xffffff. Näiden muistityyppien lisäksi on Amigassa sekä kiinteitä pseudomuistipaikkoja, joiden avulla ohjataan erikoisprosessoreita (osoitteet 0xdff???) ja CIA-piirejä (osoitteet 0xbfe?01 ja 0xbfd?00), että oheislait- teiden ohjausosoitteita. Pino (englanniksi "stack"): Jokaisella käynnissä olevalla ohjelmalla on alue muistia, jota kutsutaan pinoksi. Amigassa käynnistettävien ohjelmien pinon kokoa voi muuttaa shellistä stack-komennolla ja Workbenchistä ohjelman iko- nin tiedoilla. Pinoa käytetään esimerkiksi ohjelman kutsuessa aliohjelmia (C:ssä funktiokutsut) paluuosoitteen säilyttämiseen. Pino toimii pino-osoit- timella, joka asetetaan aluksi osoittamaan muistialueen loppuun. Aina kun pinoon tallennetaan jotain, vähennetään ensin pino-osoittimesta tallennetta- van olion koko, ja sitten asetetaan pino-osoittimen osoittamaan kohtaan olion sisältö. Pino-osoitin osoittaa siis aina viimeksi pinoon tallennetun olion alkuun. Kun pinosta otetaan jotain pois, otetaan pino-osoittimen osoittamasta kohdasta olion arvo ja lisätään pino-osoittimeen sen koko. Pi- nosta on siis aina otettava pois kaikki, mitä sinne laitetaan päinvastaises- sa järjestyksessä kuin missä ne on laitettu sinne. C-kielellä ohjelmoitaessa ei tarvitse sen enempää välittää pinon toiminnasta, mutta koska Amigalla on ohjelmilla tavallisesti kiinteät pinot, kannattaa välttää käyttämästä run- saasti pino-muistia vaativaa ohjelmointitekniikkaa. C-kielen yksinkertaisimpia tyyppejä ovat kokonaisluvut. Näitäkin on usean tyyppisiä (erikokoisia, etumerkillisiä ja etumerkittömiä). Tässä ovat tyyp- pien koot lueteltuna sellaisina kuin ne ovat yleisimmin Amigalla. Jos ei tiedä, minkä kokoisina kääntäjässä on toteutettu nämä, kannattaa katsoa inc- lude-tiedostosta "limits.h" (include-tiedostoista lisää myöhemmin). char signed char "char" on tavun mittainen eli 8-bittinen kokonaisluku. Arvo voi olla -128 - 127. Tätä tyyppiä käytetään usein tekstin yhteydessä, koska ASCII-tekstissä jokainen merkki on yksi tavu. Tämä tyyppi on etumerkillinen, eli jos eniten merkitsevä bitti on päällä, ilmaisee se luvun olevan negatiivinen. Negatiivinen arvo lasketaan vastaava- na positiivisena arvona vähennettynä nollasta, eli suurin negatiivinen luku (-1) on 0xff, pienin negatiivinen luku (-128) on 0x80. Joissain kääntäjissä on optio, jota käytettäessä "char" tarkoittaa "unsigned char":ia, ja etumerkillistä tavua varten täytyy käyttää "signed char":ia. unsigned char Kuten char, mutta etumerkitön. Arvo voi olla 0 - 255 (0x00 - 0xff). short signed short short int signed short int "short" on etumerkillinen, 16-bittinen (kahden tavun mittainen) kokonaislu- ku. 680x0-prosessoreilla short tallennetaan muistiin kahteen peräkkäiseen tavuun, eniten merkitsevä tavu ensin (pienemmässä muistiosoitteessa). 68000/68010:llä on shortin oltava parillisessa osoitteessa (shortin osoite on sen ensimmäisen tavun osoite). Arvo voi olla -32768 - 32767. unsigned short unsigned short int Kuten short, mutta etumerkitön. Arvo voi olla 0 - 65535 (0x0000 - 0xffff). int signed int "int" on yleensä samanlainen kuin "long". Joissain kääntäjissä se voi olla joko optiolla tai aina (Aztec) kuten "short". unsigned int Kuten int, mutta etumerkitön. long signed long long int signed long int "Long" on etumerkillinen, 32-bittinen (neljän tavun mittainen) kokonaisluku. 680x0-prosessoreilla long tallennetaan muistiin neljään peräkkäiseen tavuun, eniten merkitsevä tavu ensin. 68000/68010:llä on longin oltava parillisessa osoitteessa. Arvo voi olla -2147483648 - 2147383647. unsigned long unsigned long int Kuten long, mutta etumerkitön. Arvo voi olla 0 - 4294967295 (0x00000000 - 0xffffffff). long long signed long long long long int signed long long int Joissain kääntäjissä (ainakin GCC:ssä) on käytettävissä 64-bittinen koko- naislukutyyppi. 680x0-prosessorit eivät suoraan tue 64-bittisiä lukuja, jo- ten näiden käsittely on hieman monimutkaisempaa ja hitaampaa kuin muiden ko- konaislukutyyppien. Etumerkillisen, 64-bittisen kokonaisluvun arvo voi olla -9223372036854775808 - 9223372036854775807. unsigned long long unsigned long long int Kuten long long, mutta etumerkitön. Arvo voi olla 0 - 18446744073709551615 (0x0000000000000000 - 0xffffffffffffffff). {3Muuttujan määrittely Muuttujaa määriteltäessä on muuttujalle annettava jokin tyyppi ja nimi. Tyyppi voi olla esimerkiksi jokin ylläolevista kokonaislukutyypeistä. Muut- tujan nimi voi olla periaatteessa minkä pituinen vain (tosin jotkut vanha- naikaiset kääntäjät tunnistavat muuttujan vain nimen ensimmäisen kahdeksan merkin perusteella - ANSIn mukaan tarvitsee huomioida vähintään ensimmäiset 31 merkkiä) ja voi sisältää kirjaimia a-z (sekä isoja että pieniä), numeroi- ta sekä "_"-merkkejä. Muuttujan nimen ensimmäinen merkki ei saa olla numero. Isot kirjaimet ja pienet kirjaimet eivät vastaa toisiaan, joten voi olla esimerkiksi muuttuja nimeltä A ja muuttuja nimeltä a ilman, että ne sekoit- tuvat keskenään. Muuttujan nimeksi ei voi antaa mitään C-kielen kannalta jotain merkitseviä avainsanoja (kuten tyyppien nimiä). Esimerkkejä muuttujien määrittelystä: /* * määritellään muuttuja alpha tyyppiä long: */ long alpha; /* * muuttujia voidaan määritellä myös useampia kerralla: * * määritellään muuttujat a, b ja c, tyyppiä short: */ short a, b, c; /* * joka on sama asia kuin: */ short a; short b; short c; /* * määritellään muuttuja mychr tyyppiä unsigned char: */ unsigned char mychr; Usein muuttujia nimetessä kannattaa käyttää nimitystä, joka kuvaa (yleensä lyhennettynä englanninkielestä) jotenkin muuttujan tehtävää. Jos muuttujaa käytetään vain hetkellisesti, eikä sille löydy mitään loogista lyhennettä, nimetään muuttuja usein yhdellä tai kahdella kirjaimella. Muuttujien ni- meäminen esim. suomeksi tai sanoin, jotka eivät liity mitenkään asiaan, on kaikkea muuta kuin suositeltavaa. Muuttujaa voidaan määrittelyn jälkeen käyttää ohjelman siinä lohkossa (eli aaltosulkeiden välissä olevassa osassa), jossa muuttuja on määritelty. Jos muuttujan määrittely ei ole minkään funktion sisällä, on muuttuja käytettävissä kaikissa funktioissa koko loppuohjelmassa, eli se on "globaa- li" muuttuja. Muulloin on kyseessä paikallinen muuttuja. Funktion parametreissa nimetty muuttuja on käytettävissä aina kyseisen funk- tion sisällä. Muuttuja vaatii tietysti paikan, jossa sitä voi säilyttää. Säilytystilaa se tarvitsee tyyppinsä koon verran. Periaatteessa C-kääntäjä saa säilyttää muuttujia miten haluaa, mutta ainakin Amigalla tuntuvat kaikki kääntäjät te- kevän sen suunnilleen samoin. Globaalit muuttujat sijoitetaan ohjelman da- ta/bss-hunkkeihin, jotka ovat ohjelman ajon ajan kiinteästi muistissa olevia alueita. Paikallisista muuttujista osa sijoitetaan keskusyksikön rekisterei- hin (yleensä eniten käytetyt) ja osalle varataan tilaa pinosta kyseisen funktion suorituksen ajaksi. {3Lauseet ja lausekkeet C-kielinen ohjelma koostuu useimmiten pääasiassa lauseista (englanniksi "statements"), jotka ovat funktion sisällön toiminnallisia osia. Tyhjä lause olisi pelkkä ";"-merkki. Lauseet ovat usein joitakin rakenteita (if, while, for, switch jne.) tai komentoja (return, goto). Lause voi myös olla lauseke (englanniksi "expression"), jonka perässä on ";"-merkki. Myös komentolausei- den perässä on ";"-merkki. Lauseke koostuu puolestaan yleensä yhdestä tai useammasta lausekkeesta, joiden välissä tai yhteydessä on operaatioita. Pe- ruslausekkeita (eivät koostu useammasta lausekkeesta operaattoreilla yhdis- tettynä) ovat esimerkiksi muuttujat, vakiot ja funktiokutsut. Lausekkeella on lähes poikkeuksetta arvo, mutta lauseke ei välttämättä tee mitään. Yksin- kertainen esimerkki lausekkeen muodostamasta lauseesta, joka ei tee mitään: 0; Tässä on kyseessä perustyyppiä oleva lauseke, vakio. Lausekkeen arvo on va- kion arvo, eli 0. Hieman hyödyllisempi esimerkki lausekkeesta voisi olla arvon antaminen muut- tujalle. Tämä tehdään "="-operaattorilla. Esimerkiksi jos meillä on muuttuja "a", joka on jotain kokonaislukutyyppiä, voimme antaa sille arvon 0 seuraa- valla tavalla: a = 0; Operaattori "=" toimii siten, että sen molemmilla puolilla on operandeina lausekkeet. Myös tämä kokonaisuus on lauseke, joten jos meillä on muuttujat a ja b (jotka ovat jossain määrin yhteensopivan tyyppisiä): b = a = 0; Tämä antaa siis ensin muuttujalle a arvon nolla, sitten antaa muuttujalle b lausekkeen (a = 0) arvon, joka on myös 0. (Peräkkäiset "="-operaatiot suori- tetaan aina oikealta vasemmalle.) Tämä on siis sama kuin: a = 0; b = a; Operaattorin "=" vasemmalla puolella olevalla operandilla on sellainen vaa- timus, että sen on oltava muutettavissa. (Englanniksi tällaisen vaatimuksen täyttävää lauseketta kutsutaan nimellä "modifiable lvalue".) Toistaiseksi kurssissa on käsitelty vasta yksi mahdollinen lauseketyyppi, joka täyttää tämän vaatimuksen, eli muuttuja. Lausekkeita voivat olla myös yksinkertaiset matemaattiset laskutoimitukset; normaali laskujärjestys pätee ja sulkeita voidaan käyttää sen muuttamiseen: int a, b, c; a = 1 + 5 * 8; /* a:n arvoksi tulee 41 */ b = 10 / 4 - a; /* b:n arvoksi tulee -39 */ c = 2 * (a + b); /* c:n arvoksi tulee 4 */ Muita laskennallisia operaattoreita ovat: % jakojäännös Käyttö esimerkiksi: a = 5 % 3; /* a:n arvoksi tulee 2 */ & "ja"-binäärioperaattori Tuloksessa ovat tilassa 1 vain ne bitit, jotka olivat molemmissa operandeis- sa päällä, binäärilukuina esimerkiksi: 0111001011010110 (heksana 0x72d6) & 1010100110101101 (heksana 0xa9ad) antaa tulokseksi: 0010000010000100 (heksana 0x2084) Käyttö esimerkiksi: a = 0xf0f0 & 0x5555; /* a:n arvoksi tulee 0x5050 */ /* * sama desimaaliluvuilla (vaikeampi hahmottaa): */ a = 61680 & 21845; /* a:n arvoksi tulee 20560 */ | "tai"-binäärioperaattori Tuloksessa ovat kaikki ne bitit ykkösiä, jotka olivat jommassakummassa ope- randissa päällä, binäärinä esim.: 01001101 (heksana 0x4d) | 11010110 (heksana 0xd6) antaa tulokseksi: 11011111 (heksana 0xdf) Käyttö esimerkiksi: a = 0x1111 | 0x3232; /* a:n arvoksi tulee 0x3333 */ /* * sama desimaaliluvuilla: */ a = 4369 | 12850; /* a:n arvoksi tulee 13107 */ ^ "ehdoton tai"-binäärioperaattori Tuloksessa ovat kaikki ne bitit ykkösiä, jotka olivat jommassakummassa ope- randissa mutta eivät molemmissa päällä, binäärinä esim.: 10110100 (heksana 0xb4) ^ 11011001 (heksana 0xd9) antaa tulokseksi: 01101101 (heksana 0x6d) Käyttö esimerkiksi: a = 0x1f42 ^ 0x3729; /* a:n arvoksi tulee 0x286b */ /* * sama desimaaliluvuilla: */ a = 8002 ^ 14121; /* a:n arvoksi tulee 10347 */ ~ "ei"-binäärioperaattori Tämä on edeltämäänsä lausekkeeseen liittyvä operaattori (englanniksi "unary operator"). Tuloksen bitit ovat päinvastaisessa tilassa kuin operandin bitit, binäärinä esim.: ~ 01100101 (heksana 0x65) antaa tulokseksi: 10011010 (heksana 0x9a) Käyttö esimerkiksi: a = ~0x4e82; /* a:n arvoksi tulee 0xb17d */ /* * sama desimaaliluvuilla: */ a = ~20098; /* a:n arvoksi tulee 45437 */ << bittien siirto vasemmalle Antaa tulokseksi vasemman operandin bitit siirrettynä vasemmalle oikean ope- randin verran. Ellei laskussa tapahdu ylivuotoa, niin a << b on sama kuin a kerrottaisiin 2^b:llä (jossa ^ esittää potenssiin korotusta). a = 1 << 2; /* a:n arvoksi tulee 4 */ >> bittien siirto oikealle Antaa tulokseksi vasemman operandin bitit siirrettynä oikealle oikean ope- randin verran. Jos oikea operandi ei ole negatiivinen, niin a >> b on sama kuin a jaettaisiin 2^b:llä (jossa ^ esittää potenssiin korotusta). a = 12 >> 1; /* a:n arvoksi tulee 6 */ {3Vertailuoperaattorit, if-rakenne Lausekkeissa käytetään myös sellaisia operaatioita, joilla on vain kaksi mahdollista arvoa: tosi tai epätosi. Tosi tarkoittaa mitä tahansa arvoa, jo- ka ei ole nolla. Nolla on ainoa epätosi. Näitä operaatioita on kahdentyyppi- siä: vertailulliset ja loogiset operaatiot. Vertailuoperaattorit vertaavat molemmin puolin olevien lausekkeiden arvoja. Niitä ovat seuraavat: == samanarvoinen != eriarvoinen < pienempi > suurempi <= pienempi tai sama >= suurempi tai sama Näitä voidaan käyttää tähän asti esitettyyn tyyliin, eli esimerkiksi: a = b == 10; (Asettaa muuttujan a 1:ksi jos muuttujan b arvo on 10, muussa tapauksessa nollaa a:n). Tai jopa: a >= b; (Ei tee mitään, koska tulosta ei käytetä mihinkään.) Tämä ei kuitenkaan ole kovin hyödyllistä. Vertailuoperaatioita käytetään yleensä silmukoiden (myöhemmin) ja if-rakenteiden yhteydessä: if () { /* ohjelman osa, joka suoritetaan vain jos on tosi */ } /* ohjelma jatkuu */ tai: if () { /* ohjelman osa, joka suoritetaan vain jos on tosi */ } else { /* ohjelman osa, joka suoritetaan jos on epätosi */ } /* ohjelma jatkuu */ Jos "if ()" - tai "else"-kohdan jälkeen tuleva ohjelman osa muodostuu vain yhdestä lauseesta, voidaan aaltosulkeet jättää pois. Esimerkkejä if-rakenteesta: if (a == 5) { puts("a == 5 on tosi"); a = 10; } Jos muuttujan a arvo on 5, tulostaa tekstin "a == 5 on tosi" ja muuttaa a:n arvon 10:ksi. Huomaa, että lausekkeen perässä ei ole ";"-merkkiä, koska se ei tässä tapauksessa muodosta lausetta. If-rakenne sen sijaan on kokonaisuu- tena lause, johon kuuluu perässä oleva toinen lause (tässä tapauksessa useamman lauseen muodostama ohjelmalohko). if (a < b) puts("a on pienempi kuin b"); else if (a == b) { puts("a on samanarvoinen kuin b"); b = 0; } Tässä tapuksessa on ensimmäisen if:n ja else:n jälkeiset aaltosulkeet voitu jättää pois, toisen if:n jälkeen ei. Tämän voisi kirjoittaa myös jättämättä aaltosulkeita pois: if (a < b) { puts("a on pienempi kuin b"); } else { if (a == b) { puts("a on samanarvoinen kuin b"); b = 0; } } Jotkut käyttävät aina aaltosulkeita, vaikka niitä ei tarvittaisikaan. Tässäkin tapauksessa on havaittavissa, että se voi selkeyttää hieman koodin rakennetta. C-kielen grammatiikan ainoa epäselvyys on useamman vailla aaltosulkeita ole- van if:n jälkeen tulevan else:n tapaus. (Jos joku tietää, miten välttää par- serissa tämän takia ilmenevän shift/reduce-conflictin keksimättä omia ylimääräisiä sääntöjään ja monimutkaistamatta parseria pahemmin, niin kerto- koon). Jos meillä on esimerkiksi: if (b != 10) if (a) puts("text1"); else puts("text2"); Tämä näyttää selkeältä (erityisesti muotoilun ansiosta), mutta sisältää kui- tenkin epäselvyyden. Periaatteessa else voisi olla kummalle tahansa if-eh- dolle vaihtoehto. Tämä kuitenkin tulkitaan siten (kuten esimerkin muotoilus- sa on otettu huomioon), että else on vaihtoehtona aina sisimmälle mahdolli- selle if-ehdolle. Hyvä tapa on kuitenkin näissä epäselvissä tapauksissa käyttää aaltosulkeita: if (b != 10) { if (a) puts("text1"); else puts("text2"); } Tästä käy myös ilmi, että if:n ehdon ei tarvitse sisältää vertailuoperaatto- reita. Tässä sisempi if testaa, onko muuttujan a arvo tosi vai ei, eli "text2" tulostuu, jos a on 0, muuten tulostetaan "text1". {3Ensimmäinen osa päättyy... Tähän asti en ole voinut esittää useampia kokonaisia esimerkkiohjelmia, kos- ka annettu tieto itse kielestä on vielä hyvin vähäistä. Myöhemmissä osissa näitä tulee olemaan enemmän. Tarkoitus on käsitellä ainakin seuraavia aihei- ta jatkossa: - loogiset operaatiot (&&, ||, !) - operaattoreiden lyhenteitä - silmukat (for, while, do { } while) - switch - esikäsittelyn ohjaaminen - lisää tietoa tyypeistä, muuttujista ja funktioista - main()-funktion parametrit - merkkijono- ja merkkivakiot - lisää operaattoreita (*, &, ->, ., ?:, ",") - operaatioiden suoritusjärjestys - liukuluvut, liukulukuvakiot - goto-komento, miksi ei pidä käyttää - standardeja (ANSI) C-funktioita ja include-tiedostoja, niiden käyttö - tiedon käsittelytekniikkaa (muisti, tiedostot, muuttujat) - käännöksen vaiheet - suuremman projektin kasassapito - C-kääntäjän toimintaa: C-kielen teoriaa - bugien etsintä - C-laajennuksia - ohjelmointitekniikka: vihjeitä - Amigan käyttöjärjestelmä: alustavaa tietoa, mistä jatkaa